/*
 * Ext Gantt 1.2
 * Copyright(c) 2009-2011 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 * This file is partially based on Maxim TreeGrid, BSD license:
 *   Copyright (c) 2009, Maxim G. Bazhenov
 *
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *
 *    Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright notice, this 
 *    list of conditions and the following disclaimer in the documentation and/or 
 *    other materials provided with the distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 *   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 *   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 *   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
Ext.ns('Sch.gantt');

/**
 * @class Sch.gantt.GanttPanel
 * @extends Sch.AbstractSchedulerPanel
 * <p>The GanttPanel panel, encapsulating the Gantt specific functionality. It is partially based on <a href="http://www.extjs.com/forum/showthread.php?76331-TreeGrid-%28Ext.ux.maximgb.tg%29-a-tree-grid-component-based-on-Ext-s-native-grid.">MaximGB's TreeGrid</a>. This means you'll need to use a certain type of store for your data, either a {@link Ext.ux.maximgb.tg.NestedSetStore NestedSetStore} or an {@link Ext.ux.maximgb.tg.AdjacencyListStore AdjacencyListStore}. </p>
 <pre><code>
        
var start = new Date(2010,0,1),
    end = Sch.util.Date.add(start, Sch.util.Date.MONTH, 10);

var store = new Ext.ux.maximgb.tg.AdjacencyListStore({
    defaultExpanded : false,
    autoLoad : true,
    proxy : new Ext.data.HttpProxy({
        url : 'tasks.json',
        method:'GET'
    }),
    reader: new Ext.data.JsonReader({idProperty : 'Id'}, [
            // Mandatory fields
            {name:'Id'},
            {name:'Name', type:'string'},
            {name:'StartDate', type : 'date', dateFormat:'c'},
            {name:'EndDate', type : 'date', dateFormat:'c'},
            {name:'PercentDone'},
            {name:'ParentId', type: 'auto'},
            {name:'IsLeaf', type: 'bool'},

            // Your task meta data goes here
            {name:'Responsible'}
        ]
    )
});

var dependencyStore = new Ext.data.JsonStore({   
    autoLoad : true,
    proxy : new Ext.data.HttpProxy({
        url : 'dependencies.json',
        method:'GET'
    }),
    fields : [
        // 3 mandatory fields
        {name:'From'},
        {name:'To'},
        {name:'Type'}
    ]
});

var g = new Sch.gantt.GanttPanel({
    height : 600,
    width: 1000,
    renderTo : Ext.getBody(),
    highlightWeekends : false,
    showTodayLine : true,
    loadMask : true,
    
    startDate : start, 
    endDate : end, 
    viewPreset : 'monthAndYear',
    
    // Setup your static columns
    columns : [
        {
            header : 'Tasks', 
            sortable:true, 
            dataIndex : 'Name', 
            locked : true,
            width:250, 
            editor : new Ext.form.TextField()
        }
    ],
    taskStore : store,
    dependencyStore : dependencyStore
});
  </code></pre>
  <p>The viewPreset property maps to presets used to define the schedule. Possible values:</p>
 <ul>
    <li>hourAndDay</li>     
    <li>dayAndWeek</li>    
    <li>weekAndDay</li>    
    <li>weekAndMonth</li>  
    <li>monthAndYear</li>   
    <li>year</li>   
 </ul>
 * @xtype gantt
 */
Sch.gantt.GanttPanel = Ext.extend(Sch.AbstractSchedulerPanel, {
    /**
     * @cfg {String/Object} leftLabelField
     * <p>A configuration used to show/edit the field to the left of the task.
     * It can be either: </p>
        <ul>
         <li>1) a string indicating the field name in the data model or </li>
         <li>2) a custom object where you can set the following possible properties:
            <ul><li><b>dataIndex</b> : String <p class="sub-desc">The field name in the data model</p></li>
            <li><b>editor</b> : Ext.form.Field<p class="sub-desc">The field used to edit the value inline </p></li>
            <li><b>renderer</b> : Function<p class="sub-desc">, A renderer method used to render the label. The renderer is called with the 'value' and the record as parameters. </p></li>
            <li><b>scope</b> : Object <p class="sub-desc">The scope in which the renderer is called </p></li>
            </ul>
         </li>
        </ul>
     */

     /**
     * @cfg {String/Object} rightLabelField
     * A configuration used to show/edit the field to the right of the task.
     * It can be either: </p>
        <ul>
         <li>1) a string indicating the field name in the data model or </li>
         <li>2) a custom object where you can set the following possible properties:
            <ul><li><b>dataIndex</b> : String <p class="sub-desc">The field name in the data model</p></li>
            <li><b>editor</b> : Ext.form.Field<p class="sub-desc">The field used to edit the value inline </p></li>
            <li><b>renderer</b> : Function<p class="sub-desc">, A renderer method used to render the label. The renderer is called with the 'value' and the record as parameters. </p></li>
            <li><b>scope</b> : Object <p class="sub-desc">The scope in which the renderer is called </p></li>
            </ul>
         </li>
        </ul>
     */
    
    /**
     * @cfg {Boolean} highlightWeekends
     * True to highlight weekends
     */
    highlightWeekends : true,
    
    /**
     * @cfg {Boolean} enableTaskDragDrop
     * True to allow drag drop of tasks
     */
    enableTaskDragDrop : true,
    
    /**
     * @cfg {Boolean} enableDependencyDragDrop
     * True to allow creation of dependencies by using drag and drop between task terminals
     */
    enableDependencyDragDrop : true,
     
    /**
     * @cfg {Boolean} enableProgressBarResize
     * True to allow resizing of the progress bar indicator inside tasks.
     */
    enableProgressBarResize : false,

    /**
     * @cfg {Boolean} toggleParentTasksOnClick
     * True to toggle the collapsed/expanded state when clicking a parent task bar
     */
    toggleParentTasksOnClick : true,
    
    /**
     * @cfg {Boolean} recalculateParentsAfterEdit
     * True to update parent start/end dates after a task has been updated
     */
    recalculateParentsAfterEdit: true,
    
    /**
     * @cfg {Boolean} cascadeChanges
     * True to cascade changes to dependent tasks 
     */
    cascadeChanges: false,
     
   /**
    * @cfg {Boolean} showTodayLine
    * True to show a line indicating current time
    */
    showTodayLine : false,
     
    /**
     * If <code>{@link #highlightWeekends}</code> is set to true, you can access the created zones plugin through this property.
     * @type Sch.plugins.Zones
     * @property weekendZonesPlugin
     */

    /**
     * @cfg {Boolean} allowParentTaskMove
     * @ignore
     * (Not yet supported) True to allow moving parent tasks.
     */
    allowParentTaskMove: false,

    /**
     * @cfg {Object} colModel The Ext.ux.grid.LockingColumnModel to use when rendering the scheduler.
     */

    /**
    * @cfg {Function} eventRenderer 
    * An empty function by default, but provided so that you can override it. This function is called each time a task 
    * is rendered into the scheduler grid. The function should return an object with properties that will be applied to the relevant task template. 
    * By default, the task templates include placeholders for 'cls', 'style'. The cls property is a CSS class which will be added to the 
    * task bar element. The style property is an inline style declaration for the task bar element. 
    *
    * <pre>
    *  renderer : function (taskRec) {
    *      return {
    *          style : 'background-color:white',        // You can use inline styles too.
    *          cls   : taskRec.get('Priority')          // Read a property from the task record, used as a CSS class to style the event
    *          };
    *      }
    *</pre>
    * @param {Ext.data.Record} taskRecord The task about to be rendered
    * @param {Ext.data.Store} ds The task store
    * @return {Object} The data which will be applied to the task template, creating the actual HTML
    */
    eventRenderer : Ext.emptyFn,

    resizeHandles : 'both',
    
    /**
     * @cfg {Ext.Template} eventTemplate The template used to renderer leaf tasks in the gantt view. See {@link Ext.Template} for more information, see also sch.gantt.tasktemplate.js for the definition. 
     */
      
    /**
     * @cfg {Ext.Template} parentEventTemplate The template used to renderer parent tasks in the gantt view. See {@link Ext.Template} for more information, see also sch.gantt.parenttasktemplate.js for the definition
     */
    
    /**
     * @cfg {Ext.Template} milestoneTemplate The template used to renderer parent tasks in the gantt view. See {@link Ext.Template} for more information, see also sch.gantt.milestonetemplate.js for the definition. </p>
     */
    
    /**
     * An empty function by default, but provided so that you can override it. This function is called each time a task/hammock/milestone
     * is rendered into the gantt chart. The function should return an object with properties that will be applied to the template. 
     * By default, the template includes placeholders for 'cls' and 'style'. The cls property is a CSS class which will be added to the 
     * event element. The style property is an inline style declaration for the event element. 
     * If you override the default eventTemplate, you can of course include other placeholders in your template. 
     * Note: You will still need to keep the built-in placeholders for the scheduler to work. 
     *
     * <pre>
     *  renderer : function (record, row, col, ds) {
     *      return {
     *          style : 'color:white',             // You can use inline styles too.
     *          cls   : record.get('Priority')     // Read a property from the record, used as a CSS class to style the event
     *          };
     *      }
     *</pre>
     * @param {Ext.data.Record} record The task about to be rendered
     * @param {Int} row The row index
     * @param {Int} col The column index
     * @param {Ext.data.Store} ds The task store
     * @return {Object} data The data which will be applied to the template
     */
    
    /**
     * @cfg {Boolean} autoHeight NOT SUPPORTED
     * @ignore
     * @private
     */
    
    /**
     * Wrapper function returning the dependency manager instance
     * @return {Sch.gantt.DependencyManager} dependencyManager The dependency manager instance
     */
    getDependencyManager : function() {
        return this.dependencyManager;
    },
    
    /**
     * Toggle weekend highlighting
     * @param {Boolean} disabled 
     */
    disableWeekendHighlighting : function(disabled) {
        this.weekendZonesPlugin.setDisabled(disabled);
    },
    
    /**
     * @cfg {Ext.ux.maximgb.tg.AbstractTreeStore} taskStore The {@link Ext.ux.maximgb.tg.AbstractTreeStore store} holding the tasks to be rendered into the gantt chart (required).
     */
      
    /**
     * This function updates only the column header width, could be used with a slider to give a preview of the new column width before making the change (which is a potentially heavy operation)
     * @param {Int} width The new header width 
     */
    updateTimeColumnHeaderWidths : function(width) {
        var cm = this.getColumnModel();
        for (var i = cm.getLockedCount(), l = this.view.getLastTimeColumnIndex(); i < l; i++) {
	        cm.setColumnWidth(i, width, true);
	    }
	    
	    this.view.updateHeaders();
    },
    
    /**
     * <p>Returns the task record for a DOM id or a DOM node </p>
     * @param {Mixed} el The DOM node or Ext Element to lookup
     * @return {Record} The event record
     */
    resolveTask : function(el) {
        var element = Ext.get(el);
        if (!element.is(this.eventWrapSelector)) {
            element = element.up(this.eventWrapSelector);
        }
        return this.getEventRecordFromDomId(element.child(this.eventSelector).id);
    },

    /**
    * <p>Returns the event record for a DOM id </p>
    * @param {string} id The id of the DOM node
    * @return {Record} The event record
    */
    getEventRecordFromDomId : function(id) {
        var trueId = this.getEventIdFromDomNodeId(id);
        return this.taskStore.getById(trueId);
    },
    
    /**
     * <p>Tries to fit the time columns to the available view width</p>
     */
    fitTimeColumns : function() {
        this.view.fitColumns(true);
        this.view.refresh(true);
    },
  
    // Simplified method which just returns the duration in days between two dates
    // private
    getDuration : function(start, end) {
        return Math.round(Sch.util.Date.getDurationInDays(start, end)*10)/10;
    },
    
    getTaskStore : function() {
        return this.taskStore;
    },

    getDependencyStore : function() {
        return this.dependencyStore;
    },

    // Number of pixels to offset a milestone diamond
    milestoneOffset : 8,
    
    // Number of pixels to offset a parent task 
    parentTaskOffset : 6,
    
    clicksToEdit : 1,
    
    columnLines : false,
    
    // private
    eventSelector : '.sch-gantt-item',
    
    eventWrapSelector : '.sch-event-wrap',
    
    cmpCls : 'sch-ganttpanel',

    /**
     * An empty function by default, but provided so that you can perform custom validation on 
     * the item being dragged. This function is called during the drag and drop process and also after the drop is made
     * @param {Ext.data.Record} record The record being dragged
     * @param {Date} date The new start date
     * @param {Int} duration The duration of the item being dragged, in minutes
     * @param {Ext.EventObject} e The event object
     * @return {Boolean} true if the drop position is valid, else false to prevent a drop
     */
    dndValidatorFn : Ext.emptyFn,

    /**
     * An empty function by default, but provided so that you can perform custom validation on 
     * an item being resized.
     * @param {Ext.data.Record} record the task being resized
     * @param {Date} startDate the new start date
     * @param {Date} endDate the new end date
     * @param {Event} e The event object
     * @return {Boolean} true if the resize state is valid, else false to cancel
     */
    resizeValidatorFn : Ext.emptyFn,
    
    /**
     *  @cfg {Object} resizeConfig Custom config to pass to the {@link Sch.gantt.Resize} instance
     */

    // private
    constructor : function(config) {
        this.addEvents(
            // Task click-events --------------------------
            
            /**
            * @event taskclick
            * Fires when a task is clicked
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} taskRecord The task record 
            * @param {EventObject} e The event object
            */
            'taskclick', 
            
            /**
            * @event taskdblclick
            * Fires when a task is double clicked
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} taskRecord The task record 
            * @param {EventObject} e The event object
            */
            'taskdblclick', 
            
            /**
            * @event taskcontextmenu
            * Fires when contextmenu is activated on a task
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} taskRecord The task record 
            * @param {EventObject} e The event object
            */
            'taskcontextmenu', 

            // Resizing events start --------------------------
            /**
            * @event beforeresize
            * Fires before a resize starts, return false to stop the execution
            * @param {GanttPanel} scheduler The scheduler object
            * @param {Record} record The record corresponding to the element corresponding about to be resized
            * @param {EventObject} e The event object
            */
            'beforeresize', 
            
            /**
            * @event resizestart
            * Fires when resize starts
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} record The record corresponding to the element corresponding about to be resized
            */
            'resizestart', 
            
            /**
            * @event partialresize
            * Fires during a resize operation and provides information about the current start and end of the resized event
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} record The event record corresponding to the element being resized
            * @param {Date} startDate The start date of the task
            * @param {Date} endDate The end date of the task
            * @param {Ext.Element} The element being resized
            */
            'partialresize', 
            
            /**
            * @event afterresize
            * Fires after a succesful resize operation
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} record The updated record
            */
            'afterresize',
            // Resizing events end --------------------------
            
            // Task progress bar resizing events start --------------------------
            /**
            * @event beforeprogressbarresize
            * Fires before a progress bar resize starts, return false to stop the execution
            * @param {GanttPanel} scheduler The scheduler object
            * @param {Record} el The record about to be have its progress bar resized
            */
            'beforeprogressbarresize', 
            
            /**
            * @event progressbarresizestart
            * Fires when a progress bar resize starts
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} el The record about to be have its progress bar resized
            */
            'progressbarresizestart', 
           
            /**
            * @event afterprogressbarresize
            * Fires after a succesful progress bar resize operation
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Record} record The updated record
            */
            'afterprogressbarresize',
            // Task progressbar resizing events end --------------------------
            
            // Dnd events start --------------------------
            /**
            * @event beforednd
            * Fires before a dnd operation is initiated, return false to cancel it
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Ext.data.Record} record The task record that's about to be dragged
            * @param {EventObject} e The event object
            */ 
            'beforednd', 
            
            /**
            * @event dndstart
            * Fires when a dnd operation starts
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Ext.data.Record} record The record being dragged
            */
            'dndstart',
            
            /**
            * @event drop
            * Fires after a succesful drag and drop operation
            * @param {GanttPanel} gantt The gantt panel instance
            * @param {Ext.data.Record} record The dropped record
            */
            'drop',
            
            /**
            * @event afterdnd
            * Fires after a drag and drop operation, regardless if the drop valid or invalid
            * @param {GanttPanel} gantt The gantt panel instance
            */
            'afterdnd',
            // Dnd events end --------------------------
            
             /**
             * @event labeledit_beforestartedit
             * Fires before editing is started for a field
             * @param {GanttPanel} g The gantt panel instance
             * @param {Record} record The task record 
             */
            'labeledit_beforestartedit', 

            /**
             * @event labeledit_beforecomplete
             * Fires after a change has been made to a label field, but before the change is reflected in the underlying field.
             * @param {GanttPanel} g The gantt panel instance
             * @param {Mixed} value The current field value
             * @param {Mixed} startValue The original field value
             * @param {Record} record The affected record 
             */
            'labeledit_beforecomplete', 
            
            /**
             * @event labeledit_complete
             * Fires after editing is complete and any changed value has been written to the underlying field.
             * @param {GanttPanel} g The gantt panel instance
             * @param {Mixed} value The current field value
             * @param {Mixed} startValue The original field value
             * @param {Record} record The affected record 
             */
            'labeledit_complete',

            /**
             * @event dependencydblclick
             * Fires after double clicking on a dependency line/arrow
             * @param {GanttPanel} g The gantt panel instance
             * @param {Record} record The dependency record 
             */
            'dependencydblclick',

            // Dependency drag drop events --------------------------
            
            
            // Dependency drag drop end --------------------------
            /**
             * @event beforedependencydrag
             * Fires before a dependency drag operation starts (from a "task terminal").
             * @param {GanttPanel} g The gantt panel instance
             * @param {Ext.data.Record} record The source task record 
             */
            'beforedependencydrag', 

             /**
             * @event dependencydragstart
             * Fires when a dependency drag operation starts 
             * @param {GanttPanel} g The gantt panel instance
             */
            'dependencydragstart', 

            /**
             * @event dependencydrop
             * Fires when a dependency drag drop operation has completed successfully and a new dependency has been created.
             * @param {GanttPanel} g The gantt panel instance
             * @param {Ext.data.Record} fromRecord The source task record 
             * @param {Ext.data.Record} toRecord The destination task record 
             * @param {Int} type The dependency type
             */
            'dependencydrop', 

            /**
             * @event afterdependencydragdrop
             * Always fires after a dependency drag-drop operation
             * @param {GanttPanel} g The gantt panel instance
             */
            'afterdependencydragdrop'
        );

        Sch.gantt.GanttPanel.superclass.constructor.call(this, config);
    },

    // private
    configureLabels : function() {
        this.leftLabelField = this.leftLabelField || {};
        this.rightLabelField = this.rightLabelField || {};
       
        var defaults = {
            renderer : function(v) {return v;},
            dataIndex : undefined
        };

        if (Ext.isString(this.leftLabelField)) {
            this.leftLabelField = {
                dataIndex : this.leftLabelField
            };
        }

        if (Ext.isString(this.rightLabelField)) {
            this.rightLabelField = {
                dataIndex : this.rightLabelField
            };
        }

        Ext.applyIf(this.leftLabelField, defaults);
        Ext.applyIf(this.rightLabelField, defaults);

        // Initialize left editor (if defined)
        if (this.leftLabelField.editor) {
            this.leftLabelField.editor = new Sch.gantt.LabelEditor(this, {
                alignment: 'r-r',
                field : this.leftLabelField.editor,
                delegate : '.sch-gantt-label-left',
                dataIndex : this.leftLabelField.dataIndex
            });
        }

        // Initialize right editor (if defined)
        if (this.rightLabelField.editor) {
            this.rightLabelField.editor = new Sch.gantt.LabelEditor(this, {
                alignment: 'l-l',
                field : this.rightLabelField.editor,
                delegate : '.sch-gantt-label-right',
                dataIndex : this.rightLabelField.dataIndex
            });
        }

        this.on('labeledit_beforestartedit', this.onBeforeLabelEdit, this);
    },

    setupTemplates: function() {

        var tplCfg = {
            enableDependencyDragDrop : this.enableDependencyDragDrop !== false, 
            resizeHandles : this.resizeHandles, 
            enableProgressBarResize : this.enableProgressBarResize,
            showBaseline : this.showBaseline
        };
        
        if (!this.eventTemplate) {
            this.eventTemplate = new Sch.gantt.TaskTemplate(tplCfg);
        }
        
        if (!this.parentEventTemplate) {
            this.parentEventTemplate = new Sch.gantt.ParentTaskTemplate(tplCfg);
        }
    
        if (!this.milestoneTemplate) {
            this.milestoneTemplate = new Sch.gantt.MilestoneTemplate(tplCfg);
        }
    },
    
    // private 
    configureFunctionality : function() {
        // Normalize to array
        if (this.plugins && !Ext.isArray(this.plugins)) {
            this.plugins = [this.plugins];
        } else {
            this.plugins = this.plugins || [];
        }
        
        if (this.enableProgressBarResize !== false && Sch.gantt.ProgressBarResize) {
            this.progressBarResizer = new Sch.gantt.ProgressBarResize(this);
            this.on('beforeprogressbarresize', this.onBeforeTaskProgressBarResize, this);
        }

        if (this.resizeHandles !== 'none' && Sch.gantt.Resize) {
            this.taskResizer = new Sch.gantt.Resize(this, Ext.apply({
                validatorFn : this.resizeValidatorFn || Ext.emptyFn,
                validatorFnScope : this.validatorFnScope || this
            }, this.resizeConfig || {}));

            this.on('beforeresize', this.onBeforeTaskResize, this);
        }
        
        if (this.enableTaskDragDrop  && Sch.gantt.DragDrop) {
            this.taskDragDrop = new Sch.gantt.DragDrop(this, Ext.apply({
                validatorFn : this.dndValidatorFn  || Ext.emptyFn,
                validatorFnScope : this.validatorFnScope || this
            }, this.dragDropConfig));
            
            this.on('beforednd', this.onBeforeTaskDrag, this);
        }
        
        if (this.highlightWeekends) {
            this.weekendZonesPlugin = new Sch.plugins.Zones({
                store : new Ext.data.JsonStore({
                    fields : ['StartDate', 'EndDate']
                })
            });
            this.plugins.push(this.weekendZonesPlugin);
        }
        
        if (this.showTodayLine) {
            this.todayLinePlugin = new Sch.plugins.CurrentTimeLine();
            this.plugins.push(this.todayLinePlugin);
        }

        this.on('beforeedit', this.onBeforeEdit, this);
    },
    
    // private
    onBeforeTaskDrag : function(p, record) {
        return !this.readOnly && (this.allowParentTaskMove || this.taskStore.isLeafNode(record));
    },

    // private
    onBeforeTaskResize : function() {
        return !this.readOnly;
    },

    onBeforeTaskProgressBarResize : function() {
        return !this.readOnly;
    },

    onBeforeLabelEdit : function() {
        return !this.readOnly;
    },

    onBeforeEdit : function() {
        return !this.readOnly;
    },

    // private
    isMilestone : function(task) {
        return task.get('EndDate') - task.get('StartDate') === 0;
    },
    
    // private
    populateWeekendZonesPlugin : function() {
        var data = [],
            gStart = this.getStart(),
            gEnd = this.getEnd(),
            c = gStart.clone();
        
        while(c.getDay() !== 6) {
            c = Sch.util.Date.add(c, Sch.util.Date.DAY, 1);
        }
        
        while (c < gEnd) {
            data.push({
                StartDate : c,
                EndDate : c.add(Sch.util.Date.DAY, 2)
            });
            c = Sch.util.Date.add(c, Sch.util.Date.WEEK, 1);
        }
        
        this.weekendZonesPlugin.store.loadData(data);
    },
    
    // private
    getView : function(){
        if(!this.view){
            this.viewConfig = this.viewConfig || {};
            Ext.applyIf(this.viewConfig, {
                cellSelectorDepth : 22,
                rowSelectorDepth : 12
            });
            this.view = new Sch.gantt.GanttView(this.viewConfig);
        }
        return this.view;
    },
    
    // private
    initComponent : function() {
        this.configureLabels();
        this.setupTemplates();

        // Backwards compatibility
        var taskStore = this.taskStore || this.store;
        
        Ext.apply(this, {
            store : taskStore,          // For the grid panel API
            taskStore : taskStore
        });

        if (this.highlightWeekends) {
            this.on('viewchange', this.populateWeekendZonesPlugin, this);
        }
        
        if (!this.dependencyStore) {
            // Assign an empty store if one hasn't been provided
            this.dependencyStore = new Ext.data.Store();
        }
        
        if(!this.selModel && !this.disableSelection){
            this.selModel = new Sch.gantt.RowSelectionModel();
        }
        
        this.setupGanttEvents();
        this.configureFunctionality();

        Sch.gantt.GanttPanel.superclass.initComponent.call(this);
    },
    
    // private
    setupGanttEvents : function() {
        var sm = this.getSelectionModel();

        this.on({
             rowclick : function(g, rowIndex, e) {
                var t = e.getTarget(this.eventSelector);
                if (t) {
                    var task = this.taskStore.getAt(rowIndex);
                    if (!this.toggleParentTasksOnClick || !!this.taskStore.isLeafNode(task)) {
                        Ext.grid.RowSelectionModel.prototype.handleMouseDown.call(sm, this, rowIndex, e);
                    }
                    this.fireEvent('taskclick', this, task, t, e);
                }
            },
            rowdblclick : function(g, rowIndex, e) {
                var t = e.getTarget(this.eventSelector);
                if (t) {
                    this.fireEvent('taskdblclick', this, this.taskStore.getAt(rowIndex), e);
                }
            },
            rowcontextmenu : function(g, rowIndex, e) {
                var t = e.getTarget(this.eventSelector);
                if (t) {
                    this.fireEvent('taskcontextmenu', this, this.taskStore.getAt(rowIndex), e);
                }
            },
            scope : this
        });

        if (this.recalculateParentsAfterEdit) {
            this.taskStore.on({
                'update' : this.onStoreUpdate, 
                'add' : this.onStoreAddRemove, 
                'remove' : this.onStoreAddRemove, 
                scope : this
            });
        }
    },

    // private
    onDragDropStart : function() {
        if (this.dragCreatePlug) {
            this.dragCreatePlug.setDisabled(true);
        }
        
        if (this.tip) {
            this.tip.hide();
            this.tip.disable();
        }
    },
    
    // private
    onDragDropEnd : function() {
        if (this.dragCreatePlug) {
            this.dragCreatePlug.setDisabled(false);
        }
        
        if (this.tip) {
            this.tip.enable();
        }
    },
    
    // private
    afterRender : function() {    
        this.addClass([
            (this.highlightWeekends ? 'sch-ganttpanel-highlightweekends' : ''),
            (this.showBaseline ? 'sch-ganttpanel-showbaseline' : '')
        ].join(' '));
        
        this.initDependencies(); 

        Sch.gantt.GanttPanel.superclass.afterRender.apply(this, arguments);
    },
    
    // private
    initDependencies : function() {
        if (this.dependencyStore) {
            var v = this.getView(),
                dm = new Sch.gantt.DependencyManager(this, {
                    yOffset : this.showBaseline ? 3 : 7,
                    containerEl : v.scroller,
                    checkVisible : true,
                    enableDependencyDragDrop : this.enableDependencyDragDrop,
                    store : this.dependencyStore
                });
        
            dm.on({
                beforednd : this.onBeforeDependencyDrag, 
                dndstart : this.onDependencyDragStart, 
                drop : this.onDependencyDrop, 
                afterdnd : this.onAfterDependencyDragDrop,
                beforecascade : this.onBeforeCascade,
                cascade : this.onCascade,
                scope : this
            }); 

            this.dependencyManager = dm;
            
            this.mon(v.scroller, 'dblclick', function(e, t) {
                var rec = dm.getRecordForDependencyEl(t);
                this.fireEvent('dependencydblclick', this, rec, e, t);
            }, 
            this,
            {
                delegate : '.sch-dependency'
            });
        }
    },

    // private
    onBeforeDependencyDrag: function(dm, sourceTask) {
        return this.fireEvent('beforedependencydrag', this, sourceTask);
    },

    // private
    onDependencyDragStart : function(dm) {
        this.fireEvent('dependencydragstart', this);
        if (this.tip) {
            this.tip.disable();
        }
    },

    onDependencyDrop : function(dm, fromId, toId, type) {
        this.fireEvent('dependencydrop', this, this.taskStore.getById(fromId), this.taskStore.getById(toId), type);
    },

    // private
    onAfterDependencyDragDrop : function() {
        this.fireEvent('afterdependencydragdrop', this);
        
        // Enable tooltip after drag again
        if (this.tip) {
            this.tip.enable();
        }
    },
    
    // Disconnect the store 'update' listener for the view
    // private
    onBeforeCascade : function(dm, r) {
        var v = this.view;
        this.taskStore.un('update', v.onUpdate, v);
    },

    // Reconnect the store 'update' listener for the view
    // private
    onCascade : function(dm, r) {
        var v = this.view;
        this.taskStore.on('update', v.onUpdate, v);
    },

    /**
     * Returns the editor defined for the left task field
     * @return {Sch.gantt.LabelEditor} editor The editor
     */
    getLeftEditor : function() {
        return this.leftLabelField.editor;
    },

    /**
     * Returns the editor defined for the right task field
     * @return {Sch.gantt.LabelEditor} editor The editor
     */
    getRightEditor : function() {
        return this.rightLabelField.editor;
    },

     /**
     * Programmatically activates the editor for the field
     * @param {Ext.data.Record} record The task record
     */
    editLeftLabel : function(record) {
        var le = this.getLeftEditor();
        if (le) {
            le.edit(record);
        }
    },
    
    /**
     * Programmatically activates the editor for the field
     * @param {Ext.data.Record} record The task record
     */
    editRightLabel : function(record) {
        var re = this.getRightEditor();
        if (re) {
            re.edit(record);
        }
    },
    
    getDependenciesForTask : function(record) {
        return this.dependencyManager.getDependenciesForTask(record);
    },
    
    /**
     * Convenience method wrapping the dependency manager method which highlights the elements representing a particular dependency
     * @param {Mixed} record Either the id of a record or a record in the dependency store
     */
    highlightDependency : function(record) {
        this.dependencyManager.highlightDependency(record);
    },
    
    /**
     * Convenience method wrapping the dependency manager method which unhighlights the elements representing a particular dependency
     * @param {Mixed} depId Either the id of a record or a record in the dependency store
     */
    unhighlightDependency : function(record) {
        this.dependencyManager.unhighlightDependency(record);
    },
    
    // private
    getTaskById : function(id) {
        return this.taskStore.getById(id);
    },
    
    /**
     * <p> Highlights a task and optionally any dependent tasks.</p>
     * @param {Mixed} task Either a task record or the id of a task
     * @return {Record} The event record
     */
    highlightTask : function(taskId, highlightDependentTasks) {
        if (taskId instanceof Ext.data.Record) {
            taskId = taskId.id;
        }
         
        var index = this.taskStore.indexOfId(taskId);
       
        // El might not exist in DOM
        if (index >= 0) {
            this.getSelectionModel().selectRow(index, true);
        }
        
        if (highlightDependentTasks !== false) {
            this.dependencyStore.queryBy(function(dep) {
                if (dep.get('From') == taskId) {
                    this.highlightDependency(dep.id);
                    this.highlightTask(dep.get('To'), highlightDependentTasks);
                }
            }, this);
        }
    },
    
    // private
    clearSelectedTasksAndDependencies : function() {
        this.getSelectionModel().clearSelections();
        this.getView().el.select('.sch-dependency-selected').removeClass('sch-dependency-selected');
    },
    
    /* Maxim TreeGrid */
    /**
     * @cfg {String|Integer} master_column_id Master column id. Master column cells are nested.
     * Master column cell values are used to build breadcrumbs.
     */
    master_column_id : 0,

    // private
    onClick : function(e) {
        var target = e.getTarget('.ux-maximgb-tg-elbow-active', 1) || 
                     (this.toggleParentTasksOnClick && e.getTarget('.sch-gantt-parenttask-bar'));
        
        // Row click
        if (target) {
            var view = this.getView(),
                row = view.findRowIndex(target),
                store = this.getTaskStore(),
                record = store.getAt(row);
                
            if (store.isExpandedNode(record)) {
                store.collapseNode(record);
            } else {
                store.expandNode(record);
            }
        } 
        if (!target || target.className.match('sch-gantt-parenttask-bar')) {
            Sch.gantt.GanttPanel.superclass.onClick.call(this, e);
        }
    },

    // private
    onMouseDown : function(e) {
        if (!e.getTarget('.ux-maximgb-tg-elbow-active', 1)) {
            Sch.gantt.GanttPanel.superclass.onMouseDown.call(this, e);
        }
    },
    
    // private
    onStoreAddRemove : function(store, rec) {
        this.taskStore.suspendEvents(true);
        this.recalculateParents(rec);
        this.taskStore.resumeEvents();
    },
    
    // private
    onStoreUpdate : function(store, rec, operation, prev) {
        // No need to recalculate for commits
        if (operation !== Ext.data.Record.COMMIT && (!prev || prev.StartDate || prev.EndDate)) {
            this.onStoreAddRemove(store, rec);
        }
    },
    
    // Recalculates parent start/end dates
    // private
    recalculateParents : function(rec) {
        rec = Ext.isArray(rec) ? rec[0] : rec;
        var earliest = new Date(9999,0,0), 
            latest = new Date(0),
            parent = this.taskStore.getNodeParent(rec);
        
        if (parent) {
            var children = this.taskStore.getNodeChildren(parent),
                tStart, tEnd;

            if (children.length > 0) {
                Ext.each(children, function(r) {
                    tStart = r.get('StartDate');
                    tEnd = r.get('EndDate');
                    
                    earliest = Sch.util.Date.min(earliest, tStart || earliest);
                    latest = Sch.util.Date.max(latest, tEnd || latest);
                });
                
                if (parent.get('StartDate') - earliest !== 0) {
                    parent.set('StartDate', earliest);
                }
                if (parent.get('EndDate') - latest !== 0) {
                    parent.set('EndDate', latest);
                }
            }
            
            if (this.cascadeChanges) {
                this.getDependencyManager().performCascade(parent);
            }
            
            this.recalculateParents(parent);
        }
    },
    
    /**
     * Returns the critical path(s) that can affect the end date of the project
     * @return {Array} paths An array of arrays (containing task chains)
     */
    getCriticalPaths : function() {
        return this.getDependencyManager().getCriticalPaths();
    },

     /**
     * Highlights the critical path(s) that can affect the end date of the project
     */
    highlightCriticalPaths : function() {
        // First clear any selected tasks/dependencies
        this.clearSelectedTasksAndDependencies();
        
        var paths = this.getCriticalPaths(),
            dm = this.getDependencyManager(),
            ds = this.dependencyStore,
            t,i,l, depRecord;
        
        Ext.each(paths, function(tasks) {
            for (i = 0, l = tasks.length; i < l; i++) {
                t = tasks[i];
                this.highlightTask(t, false);
                
                if (i < (l - 1)) {
                    depRecord = ds.getAt(ds.findBy(function(dep) { return dep.get('To') === t.id && dep.get('From') === tasks[i+1].id; }));
                    dm.highlightDependency(depRecord);
                }
            }
        }, this);
        
        this.el.addClass('sch-gantt-critical-chain');
    },
    
    /**
     * Removes the highlighting the critical path(s)
     */
    unhighlightCriticalPaths : function() {
        this.el.removeClass('sch-gantt-critical-chain');
        this.clearSelectedTasksAndDependencies();
    },

    //private
    onDestroy : function() {
        this.dependencyManager.destroy();
        Sch.gantt.GanttPanel.superclass.onDestroy.apply(this, arguments);
    },

    //private
    getXOffset : function(task) {
        var offset = 0;
        
        if (this.isMilestone(task)) {
            offset = this.milestoneOffset;
        } else if (!this.taskStore.isLeafNode(task)) {
            offset = this.parentTaskOffset;
        }

        return offset;
    },

    //private
    stopEditing : function() {
        Sch.gantt.GanttPanel.superclass.stopEditing.apply(this, arguments);

        var lEd = this.getLeftEditor(),
            rEd = this.getRightEditor();
        if (lEd) {
            lEd.completeEdit();
        }

        if (rEd) {
            rEd.completeEdit();
        }
    },

    zoomToFit : function() {
       // TODO
    },

    //private
   isCellEditable : function(colIndex, rowIndex) {
        var dataIndex = this.getColumnModel().getDataIndex(colIndex),
            ganttEditable = this.taskStore.isLeafNode(this.taskStore.getAt(rowIndex)) || (dataIndex !== 'Duration' && dataIndex !== 'StartDate' && dataIndex !== 'EndDate');
       
        return ganttEditable;
    },


    // private
    // Overridden to only walked locked cells
    walkCells : function(row, col, step, fn, scope){
        var cm    = this.colModel,
            clen  = cm.getLockedCount(),
            ds    = this.taskStore,
            rlen  = ds.getCount(),
            first = true;

        if(step < 0){
            if(col < 0){
                row--;
                first = false;
            }
            while(row >= 0){
                if(!first){
                    col = clen-1;
                }
                first = false;
                while(col >= 0){
                    if(fn.call(scope || this, row, col, cm) === true){
                        return [row, col];
                    }
                    col--;
                }
                row--;
            }
        } else {
            if(col >= clen){
                row++;
                first = false;
            }
            while(row < rlen){
                if(!first){
                    col = 0;
                }
                first = false;
                while(col < clen){
                    if(fn.call(scope || this, row, col, cm) === true){
                        return [row, col];
                    }
                    col++;
                }
                row++;
            }
        }
        return null;
    },

    getTooltipRecord : function(el) {
        return this.resolveTask(el);
    }
}); 

Ext.reg('gantt', Sch.gantt.GanttPanel);

